﻿namespace Jsl.FxCop
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Checks that events are raised correctly.
    /// </summary>
    /// <remarks>
    /// The event should put into a local variable, checked to make sure it is not null and then raised. A local variable
    /// must be used to ensure multithreading works correctly.
    /// </remarks>
    public class RaiseEventsCorrectly : BaseIntrospectionRule
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="RaiseEventsCorrectly"/> class.
        /// </summary>
        public RaiseEventsCorrectly()
            : base(typeof(RaiseEventsCorrectly).Name, "Jsl.FxCop.Rules", typeof(RaiseEventsCorrectly).Assembly)
        {
        }
        #endregion Constructors

        #region Public Methods
        /// <summary>
        /// Checks the specified member.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>The problems found, if any.</returns>
        public override ProblemCollection Check(Member member)
        {
            // Only process methods.
            var method = member as Method;
            if (method == null)
            {
                return this.Problems;
            }

            var flow = new StatementFlow(method);
            this.CheckExpressionStatements(
                flow,
                flow.Statements.OfType<ExpressionStatement>());

            return this.Problems;
        }
        #endregion Public Methods

        #region Private Methods
        private void CheckExpressionStatement(StatementFlow flow, ExpressionStatement expressionStatement)
        {
            (expressionStatement.Expression as MethodCall)
                .DoIfNotNull(call => this.CheckMethodCall(flow, call));
        }

        private void CheckExpressionStatements(StatementFlow flow, IEnumerable<ExpressionStatement> expressionStatements)
        {
            foreach (var expressionStatement in expressionStatements)
            {
                this.CheckExpressionStatement(flow, expressionStatement);
            }
        }

        private void CheckMethodCall(StatementFlow flow, MethodCall methodCall)
        {
            (methodCall.Callee as MemberBinding)
                .IfNotNull(
                // Get the Invoke method if it was called.
                    callee => callee.IfNotNull(binding => binding.BoundMember as Method)
                        .If(method => method.Name.Name == "Invoke"),
                // Get the EventNode if this is one.
                    callee => callee.IfNotNull(callBinding => callBinding.TargetObject as MemberBinding)
                        .IfNotNull(targetBinding => targetBinding.BoundMember as Field)
                        .IfNotNull(eventField => eventField.GetEventNode()))
                .DoIfNotNull(
                    (_, eventNode) =>
                    {
                        this.Problems.Add(
                            new Problem(
                                this.GetNamedResolution("UseLocalVariable", eventNode.Name.Name)));
                    });
        }
        #endregion Private Methods
    }
}